#ifndef LIBACCENT_ALGORITHMS_ZIP_HPP
#define LIBACCENT_ALGORITHMS_ZIP_HPP

#include "accent/core.hpp"
#include "accent/utility/tag_functions.hpp"
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/container/vector/convert.hpp>
#include <boost/fusion/sequence/intrinsic/front.hpp>
#include <boost/fusion/sequence/intrinsic/back.hpp>
#include <boost/fusion/sequence/comparison/equal_to.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/query/all.hpp>
#include <boost/fusion/algorithm/query/any.hpp>
#include <boost/fusion/algorithm/transformation/transform.hpp>
#include <boost/fusion/algorithm/transformation/zip.hpp>
#include <boost/fusion/support/is_sequence.hpp>
#include <boost/mpl/and.hpp>
#include <utility>


namespace boost { namespace fusion {
  // Introduce a swap for sequence-of-reference where it will be found.
  namespace detail
  {
    struct swap
    {
      using result_type = void;

      template<typename Elem>
      void operator()(Elem const& e) const {
        using std::swap;
        swap(front(e), back(e));
      }
    };
  }

  template<typename Seq1, typename Seq2>
  typename std::enable_if<
    mpl::and_<traits::is_sequence<Seq1>, traits::is_sequence<Seq2>>::value,
    void
  >::type
  swap(const Seq1& lhs, const Seq2& rhs)
  {
    for_each(zip(lhs, rhs), detail::swap());
  }
}}

namespace accent
{

  namespace detail
  {

    namespace fu = boost::fusion;

    template <typename... Ranges>
    class zip_range
    {
      using holder = fu::vector<Ranges...>;
      holder parts;

      struct empty_ {
        using result_type = bool;
        template <typename R>
        bool operator ()(const R& r) const { return r.empty(); }
      };

      struct front_ {
        template <typename R>
        struct result { using type = decltype(std::declval<R>().front()); };

        template <typename R>
        typename result<R>::type operator ()(const R& r) const {
          return r.front();
        }
      };

      struct pop_front_ {
        using result_type = void;
        template <typename R>
        void operator ()(R& r) const { r.pop_front(); }
      };

      template <typename Vector, typename Fn>
      static auto transform_s(const Vector& parts, Fn fn)
        -> typename fu::result_of::as_vector<
             typename fu::result_of::transform<Vector, Fn>::type
           >::type
      {
        return fu::as_vector(fu::transform(parts, fn));
      }

      template <typename Fn>
      auto transform(Fn fn) const -> decltype(transform_s(parts, fn))
      {
        return transform_s(parts, fn);
      }

      using reference = decltype(transform_s(parts, front_()));

    public:
      using traversal = support::minimum_tag<forward_traversal_tag,
                                             typename Ranges::traversal...>;

      zip_range(holder ranges) : parts(ranges) {}

      holder ranges() const { return parts; }

      bool empty() const {
        return fu::any(parts, empty_());
      }

      reference front() const {
        return transform(front_());
      }

      void pop_front() {
        fu::for_each(parts, pop_front_());
      }

      class position
      {
        friend class zip_range;

        struct front_position_ {
          template <typename R>
          struct result {
            using type = decltype(std::declval<R>().front_position());
          };
          template <typename R>
          typename result<R>::type operator ()(const R& r) const {
            return r.front_position();
          }
        };

        using positions = decltype(transform_s(std::declval<holder>(),
                                               front_position_()));
        positions inner;

        position(const holder& parts)
          : inner(transform_s(parts, front_position_()))
        {}

        struct bool_ {
          using result_type = bool;

          template <typename P>
          bool operator ()(const P& p) const { return static_cast<bool>(p); }
        };

        struct deref {
          template <typename P>
          struct result { using type = decltype(*std::declval<P>()); };

          template <typename P>
          typename result<P>::type operator ()(const P& p) const { return *p; }
        };

      public:
        position() : inner() {}

        explicit operator bool() const {
          return fu::all(inner, bool_());
        }
        bool operator !() const { return !static_cast<bool>(*this); }
        reference operator *() const {
          return transform_s(inner, deref());
        }
        friend bool operator ==(const position& lhs, const position& rhs) {
          return lhs.inner == rhs.inner;
        }
      };

    private:
      struct from_ {
        template <typename R, typename P>
        struct result { using type = R; };
        template <typename R, typename P>
        typename result<R, P>::type operator ()(const R& r, const P& p) const {
          return r.from(p);
        }
      };
      struct until_ {
        template <typename R, typename P>
        struct result { using type = R; };
        template <typename R, typename P>
        typename result<R, P>::type operator ()(const R& r, const P& p) const {
          return r.until(p);
        }
      };

    public:
      position front_position() const {
        return position(parts);
      }
      zip_range from(position p) const {
        return { fu::transform(parts, p.inner, from_()) };
      }
      zip_range until(position p) const {
        return { fu::transform(parts, p.inner, until_()) };
      }
    };

  }

  template <typename... Ranges>
  detail::zip_range<Ranges...> zip(Ranges... ranges) {
    return { { ranges... } };
  }

}

#endif
